module CodeGraphs
  
  class Graph
    
    class Node
      attr_accessor :id, :name, :weight, :adjacencies

      def initialize id, name = nil, weight = 1
        @id = id.to_s
        @name = (name || id).to_s
        @weight = weight
        @adjacencies = {}
      end

      def join other, weight = 1
        @adjacencies[other] ||= {
          :nodeTo => other.id,
          :data => {:weight => 0}
        }
        @adjacencies[other][:data][:weight] += weight
      end

      def friends
        @adjacencies.keys
      end
      
      def group group = [self]
        friends.each do |f|
          unless group.include? f
            group << f
            f.group group
          end
        end
        group
      end

      alias_method :_inspect, :inspect
      
      def inspect
        to_s
      end
      
      
      def to_s
        @id#, @name, @friends].inspect
      end

      def dump
        { 
          :id => @id,
          :name => @name,
          :data => [{ 
              :key => "weight",
              :value => 1
            }],
          :adjacencies => @adjacencies.values
        }
      end
    end
  
    
    def initialize
      @nodes = []
      @nodes_by_ids = {}
    end
    
    def add node
      @nodes << node
      @nodes_by_ids[node.id] = node
    end
    
    def node id
      @nodes_by_ids[id] || add(Node.new(id))
    end
      
    def remove node
      @nodes = @nodes - [node]
      @nodes_by_ids.delete node.id
      # remove friends..
    end  
    
    def nodes
      @nodes
    end
    
    def join node, node2
      unless @nodes.include?(node)
        raise "graph doesnt include #{node}"
      end
      unless @nodes.include?(node2)
        raise "graph doesnt include #{node2}"
      end
      node.join node2
      node2.join node
      nil
    end
    
    def groups
      ungrouped = nodes.dup
      groups = []
      while ungrouped.length > 0
        group = ungrouped.pop.group
        ungrouped = ungrouped - group
        groups << group
      end
      groups
    end
    
    def validate_groups groups = nil
      (groups ||= self.groups).each do |group|
        group.each do |node|
          node.friends.each do |friend|
            unless group.include? friend
              message = "Group #{group.inspect} doesn't include node #{friend}, which is friend of #{node}."              
              unless @nodes.include? friend
                message += " Even worse, the node #{friend} is not among nodes in graph."
              end
              raise message
            end
          end          
        end
      end
      groups
    end
    
    def dump
      g = groups
      validate_groups g
      largest_group(g).collect { |n| n.dump}.to_json
    end
    
    def largest_group groups = nil
      (groups || self.groups).max { |a, b| a.length <=> b.length }
    end
  end
end